<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>1.3-循环中的块级绑定</title>
</head>
<body>

</body>
<script>

    /////////////////////////////////////////  1.3. 循环中的块级绑定（Block Binding in Loops） START ///////////////////////////////////////////
    console.group('--------- 1.3. 循环中的块级绑定（Block Binding in Loops）---------');

    for (var i = 0; i < 10; i++) {
        //process(items[i]);
    }

    // 这里仍然可以访问到 i
    console.log(i);                     // 10
    console.groupEnd();
    /*
        块级作用域在其它语言内部是默认的，以上的代码的执行过程也并无差异，但区别在于变量 i 只能在循环代码块内部使用。
        然而在 JavaScript中，变量的提升导致块外的部分在循环结束后依然可以访问 i 。
        若将 var 替换为 let 则更符合预期：

        for (let i = 0; i < 10; i++) {
            process(items[i]);
        }


        // 在这里访问 i 会抛出错误
        console.log(i);

        在本例中变量 i 只存在于 for 循环代码块中，一旦循环完毕变量 i 将不复存在。
     */

    //====================================1.3.1. 循环中的函数（Functions in Loops=============================
    /*
    长久以来 var 声明的特性使得在循环中创建函数问题多多，因为循环中声明的变量在块外也可以被访问，考虑如下的代码：

    var funcs = [];

    for (var i = 0; i < 10; i++) {
        funcs.push(function() { console.log(i); });
    }

    funcs.forEach(function(func) {
        func();     // 输出 "10" 共10次
    });
    你可能认为这段代码只是普通的输出 0 - 9 这十个数字，但事实上它会连续十次输出 “10”。这是因为每次迭代的过程中 i 是被共享的，意味着循环中创建的函数都保持着对相同变量的引用。当循环结束后 i 的值为 10，于是当 console.log(i)被调用后，该值会被输出。

    为了修正这个问题，开发者们在循环内部使用即时调用函数表达式（immediately-invoked function expressions, IIFEs）来迫使每次迭代时创建一份当前索引值的拷贝，示例如下：

        var funcs = [];

        for (var i = 0; i < 10; i++) {
            funcs.push((function(value) {
                return function() {
                    console.log(value);
                }
            }(i)));
        }

        funcs.forEach(function(func) {
            func();     // 输出 0，1，2 ... 9
        });
    这种写法在循环内部使用了 IIFE，并将变量 i 的值传入 IIFE 以便拷贝索引值并存储起来，这里传入的索引值为同样被当前的迭代所使用，所以循环完毕后每次调用的输出值正如所期待的那样是 0 - 9 。幸运的是，ECMAScript 6 中 let 和 const 的块级绑定对循环代码进行简化。
     */
    console.group('---------------使用 var 声明和 IIFE---------------------')
    var funcs = [];

    for (var i = 0; i < 10; i++) {
        funcs.push((function(value) {
            return function() {
                console.log(value);
            }
        })(i));
        // funcs.push((function(value) {
        //     return function() {
        //         console.log(value);
        //     }
        // }(i)));
    }

    funcs.forEach(function(func) {
        func();     // 输出 0，1，2 ... 9
    });
    console.groupEnd();

    //=====================================1.3.2. 循环中的 let 声明（Let Declarations in Loops)==========================
    console.group('--------------------1.3.2. 循环中的 let 声明（Let Declarations in Loops)--------------------------');
    var funcsUseLet = [];

    for (let i = 0; i < 10; i++) {
        funcsUseLet.push(function() {
            console.log(i);
        });
    }

    funcsUseLet.forEach(function(func) {
        func();     // 输出 0，1，2 ... 9
    });
    console.groupEnd();
    /*
    这段循环代码的执行效果完全等同于使用 var 声明和 IIFE，但显然更加简洁。
    let 声明使得每次迭代都会创建一个变量 i，
    所以循环内部创建的函数会获得各自的变量 i 的拷贝。
    每份拷贝都会在每次迭代的开始被创建并被赋值。
    这同样适用于 for-in 和 for-of 循环，如下所示：
     */
    console.group('--------------------同样适用于 for-in 和 for-of 循环，如下所示：--------------------------');
    var fs = [],
        object = {
            a: true,
            b: true,
            c: true
        };

    for (let key in object) {
        fs.push(function() {
            console.log(key);
        });
    }

    fs.forEach(function(func) {
        func();     // 输出  "a"，"b" 和 "c"
    });

    console.table(object,['a','b','c']);
    console.groupEnd();
    /*
    在本例中，for-in 循环的表现和 for 循环相同。
    每次循环的开始都会创建一个新的变量 key 的绑定，
    所以每个函数都会有各自 key 变量值的备份。
    结果就是每个函数都会输出不同的值。
    如果循环中 key 由 var 声明，那么所有的函数会输出 "c" 。

    不得不提的是，let 声明在上述循环内部中的表现是在规范中特别定义的，和非变量提升这一特性没有直接的关系。实际上，早期 let 的实现并不会表现中这种效果，它是在后来被添加到规范中的。

     */
    //======================================1.3.3. 循环中的 const 声明（Constant Declarations in Loops）==============
    console.group('--------------------1.3.3. 循环中的 const 声明（Constant Declarations in Loops）--------------------------');
    /*
    ECMAScript 6 规范中没有明确禁止在循环中使用 const 声明，
    不过其具体的表现要取决于你使用哪种循环方式。
    对于普通 的 for 循环你可以在初始化（initializer）语句里使用 const 声明，
    但当你想要修改该声明变量时循环会报错：

        var funcs = [];

        // throws an error after one iteration
        for (const i = 0; i < 10; i++) {
            funcs.push(function() {
                console.log(i);
            });
        }
    在这段代码中，变量 i 被声明为常量。在循环开始迭代，即 i 为 0 的时候，代码是可以运行的，
    不过当步骤执行到 i++ 的那一刻会发生错误，因为你正在修改一个常量。
    由此，只有在循环过程中不更改在初始化语句中声明的变量的条件下，才能在里面使用 const 声明。

    另外，当使用 for-in 或 for-of 循环时，const 声明的变量的表现和 let 完全一致。所以以下的代码不会出错：
     */
    var funcsUseConst = [],
        object = {
            a: true,
            b: true,
            c: true
        };

    // 不会报错
    for (const key in object) {
        funcsUseConst.push(function() {
            console.log(key);
        });
    }

    funcsUseConst.forEach(function(func) {
        func();     // 输出 "a"，"b" 和 "c"
    });
    /*
    这段代码的作用和 “循环中的 let 声明” 一节的第二个示例相同，唯一的差异是变量 key 的值不能被修改。
    for-in 和 for-of 循环能正常使用 const
    是因为每次迭代都会创建一个新的变量绑定而不是去试图修改已存在的绑定（如上例）。
     */
    console.groupEnd();


    /////////////////////////////////////////  1.3. 循环中的块级绑定（Block Binding in Loops） END ///////////////////////////////////////////
</script>
</html>